package com.ostrich.cloud.ostrichGoodsService.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ostrich.cloud.common.exceptions.OstrichException;
import com.ostrich.cloud.common.result.Result;
import com.ostrich.cloud.ostrichGoodsSercice.model.Dto.MessageDto;
import com.ostrich.cloud.ostrichGoodsSercice.model.Dto.SecKillDto;
import com.ostrich.cloud.ostrichGoodsSercice.model.param.SnappedGoodParam;
import com.ostrich.cloud.ostrichGoodsService.common.exception.OstrichGoodsException;
import com.ostrich.cloud.ostrichGoodsService.common.status.SecKillStatus;
import com.ostrich.cloud.ostrichGoodsService.infra.mapper.GoodsMapper;
import com.ostrich.cloud.ostrichGoodsService.infra.mapper.SecKillMapper;
import com.ostrich.cloud.ostrichGoodsService.infra.mapper.SecKillOrderMapper;
import com.ostrich.cloud.ostrichGoodsService.infra.model.Goods;
import com.ostrich.cloud.ostrichGoodsService.infra.model.SecKill;

import com.ostrich.cloud.ostrichGoodsService.infra.model.SecKillOrder;
import com.ostrich.cloud.ostrichGoodsService.service.SecKillService;
import com.ostrich.cloud.ostrichOrderService.client.OrderManagerClient;
import com.ostrich.cloud.ostrichOrderService.client.param.AddKillParam;
import com.ostrich.cloud.ostrichOrderService.model.dto.OrderDto;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.messaging.support.MessageBuilder;

import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.ZoneOffset;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

import static com.ostrich.cloud.ostrichGoodsService.common.exception.OstrichGoodsExceptionCode.*;

/**
 * <p>
 * 秒杀表 服务实现类
 * </p>
 *
 * @author Lucas
 * @since 2022-10-31
 */
@Service


@Slf4j
public class SecKillServiceImpl extends ServiceImpl<SecKillMapper, SecKill> implements SecKillService, InitializingBean {

    @Resource
    private SecKillMapper secKillMapper;

    @Autowired
    private RedissonClient redissonClient;

    @Resource
    private OrderManagerClient orderManagerClient;

    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @Autowired
    private RedisTemplate redisTemplate;
   @Resource
    private SecKillOrderMapper secKillOrderMapper;

   @Resource
    private GoodsMapper goodsMapper;

    @Override
    public SecKill addSecKill(SecKill secKill) throws Exception {
        secKill.setAddTime(new Date());
        secKill.setSale(0);
        secKill.setStatus(SecKillStatus.BEGIN.getMessage());
        secKillMapper.insert(secKill);
        afterPropertiesSet();
        return secKill;

    }

    @Override
    public List<SecKillDto> getSecKill(String status) {
        List<SecKillDto> allSecKillByStatus = secKillMapper.getAllSecKillByStatus(status);
        if (allSecKillByStatus.size() == 0){
            throw new OstrichException(SEC_KILL_NOTHING_EXCEPTION);
        }
       return allSecKillByStatus;
    }

    @Override
    public void snappedGood(SnappedGoodParam snappedGoodParam, Integer parentId) {
        RLock lock = redissonClient.getLock("lock:" + parentId);
        lock.lock(90, TimeUnit.MILLISECONDS);
        MessageDto messageDto;
        try {
            ValueOperations valueOperations = redisTemplate.opsForValue();

            SecKillOrder s = (SecKillOrder) redisTemplate.opsForValue().get("order:" + parentId + ":" + snappedGoodParam.getSecId());
            if (s != null) {
                throw new OstrichGoodsException(COPY_BUY_SEC_GOODS);
            }
            Integer o = (Integer) redisTemplate.opsForValue().get("secKillGoods:" + snappedGoodParam.getSecId());
            log.info("查出来的东西{}", o);
            Long stock = valueOperations.decrement("secKillGoods:" + snappedGoodParam.getSecId());
            log.info("剩余库存数量{}", stock);
            if (stock < 0) {
                valueOperations.increment("secKillGoods:" + snappedGoodParam.getSecId());
                throw new OstrichGoodsException(SEC_KILL_NOTHING_EXCEPTION);
            }
            /**
             *  TODO 消息队列发送生成订单
             */
            SecKillOrder secKillOrder = new SecKillOrder();
            secKillOrder.setSecId(snappedGoodParam.getSecId());
            secKillOrder.setParentId(Integer.valueOf(parentId));
            messageDto = new MessageDto(Integer.valueOf(parentId), snappedGoodParam.getSecId(),snappedGoodParam.getLocationId());
            SecKill secKill = secKillMapper.selectById( snappedGoodParam.getSecId());

            long l = secKill.getEndTime().toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
            long time = new Date().getTime();
            long expire = l-time;

            redisTemplate.opsForValue().set("order:" + messageDto.getParentId() + ":" + messageDto.getSecId(), secKillOrder,expire,TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            throw new OstrichException(FAST_EXCEPTION);
        } finally {
            lock.unlock();
        }
        //判断是否重复抢购
        rocketMQTemplate.asyncSend("secKill_group", MessageBuilder.withPayload(messageDto).build(),
                new SendCallback() {
                    @Override
                    public void onSuccess(SendResult sendResult) {
                        log.info("发送到消息队列");
                    }

                    @Override
                    public void onException(Throwable throwable) {

                    }
                });
    }

    @Override
    @GlobalTransactional
    public void addSecKillOrder(SecKillOrder sec,Integer locationId) {
        //修改库存
        log.info("开始修改库存信息{}",sec);
     int update = secKillMapper.update(null, new UpdateWrapper<SecKill>()
                .setSql("stock=stock-1")
                .eq("sec_id", sec.getSecId())
                .gt("stock", 0));
        if (update<1){
            throw new OstrichGoodsException(STOCK_DEC_EXCEPTION);
        }
        SecKill secKill = secKillMapper.selectById(sec.getSecId());
        Goods goods = goodsMapper.selectById(secKill.getGoodsId());
        AddKillParam addKillParam = new AddKillParam();
        addKillParam.setBusinessmanId(goods.getBusinessmanId());
        addKillParam.setNotes("秒杀订单");
        addKillParam.setCustomerLocationId(locationId);
        addKillParam.setTotalAmount(secKill.getPrice());
        OrderDto kill = orderManagerClient.createKill(addKillParam);
        log.info("开始插入数据库{}",kill);
        sec.setOrderId(kill.getOrderId());
        secKillOrderMapper.insert(sec);
    }

    @Override
    public SecKillDto getSecKillResult(Integer parentId,Integer secId) {
        List<SecKillOrder> secKillOrders = secKillOrderMapper.selectList(new QueryWrapper<SecKillOrder>()
                .eq("parent_id", parentId)
                .eq("sec_id", secId));
        if (secKillOrders==null||secKillOrders.size()==0){
            throw new OstrichGoodsException(SEC_KILL_NO_SUCCESS);
        }else {
            SecKillDto secKillResult = secKillMapper.getSecKillResult(secKillOrders.get(0).getSecId());
            return secKillResult;
        }
    }

    /**
     * 取消秒杀订单
     * @param parentId 用户
     * @param orderId 订单id
     */
    @Override
    @GlobalTransactional
    public void cancelSecKill(Integer parentId, Integer orderId){
        SecKillOrder secKillOrder = secKillOrderMapper.selectOne(new QueryWrapper<SecKillOrder>().eq("order_id", orderId));
        int i = secKillOrderMapper.deleteById(secKillOrder.getSecKillOrderId());
        if (i==0){
            throw new OstrichException(DELETE_SEC_KILL_ORDER_EXCEPTION);
        }
        int sec_id = secKillMapper.update(null, new UpdateWrapper<SecKill>()
                .setSql("stock=stock+1")
                .eq("sec_id", secKillOrder.getSecId()));
        if (sec_id==0){
            throw new OstrichException(STOCK_DEC_EXCEPTION);
        }
        ValueOperations valueOperations = redisTemplate.opsForValue();
        Long increment = valueOperations.increment("secKillGoods:" + secKillOrder.getSecId());
        redisTemplate.delete("order:" +parentId + ":" +secKillOrder.getSecId());
        log.info("redis返回库存{}",increment);
    }


    @XxlJob("closeSecKill")
    public void closeSecKill(){
        String jobParam = XxlJobHelper.getJobParam();
        int update = secKillMapper.update(null, new UpdateWrapper<SecKill>().eq("sec_id", jobParam)
                .set("status", SecKillStatus.CLOSE.getMessage()));
        if (update==0){
            throw new OstrichException(SEC_KILL_CLOSE_EXCEPTION);
        }
    }


    @Override
    public void afterPropertiesSet() throws Exception {
        List<SecKill> secKills = secKillMapper.selectList(null);
        if (secKills.size()==0){
            return;
        }
        secKills.forEach(secKill -> {
            long l = secKill.getEndTime().toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
            long time = new Date().getTime();
            long expire = l-time;
           log.info("过期时间是{}",expire);
            if (!(secKill.getStock()==0 ||SecKillStatus.CLOSE.getMessage().equals(secKill.getStatus())||expire<0) ){
                redisTemplate.opsForValue().set("secKillGoods:"+secKill.getSecId(),secKill.getStock(),expire,TimeUnit.MILLISECONDS);
            }
        });
    }
}
